<?php
/*
classe arrayPath

Cette classe permet d'accéder à un tableau multidimensionnel via une syntaxe d'adressage. Deux syntaxes sont supportées, slashes et brackets.

méthodes publiques :

	bool load(array data) : référence un tableau comme données de travail.
	void setSyntax(string syntax) : définit la syntaxe par défaut, slashes ou brackets.
	string getSyntax(void) : retourne la syntaxe par défaut.
	mixed get(string path, string syntax) : renvoie la ou les valeurs pointées par le chemin.
	mixed getRef(string path, string syntax) : renvoie une ou des références sur le chemin pointé.
	bool set(string path, mixed value, string syntax) : affecte une valeur au(x) chemin(s) pointé(s).
	bool setContext(string path, string syntax) : indique un contexte pour les chemins relatifs.
	string getContext(void) : retourne le contexte actuel pour les chemins relatifs.

Syntaxes :

Prenons en exemple le tableau suivant :
array( 'nom' => 'toto', 'collections'=>array( 0 => array( 'objet'=>'porte-clefs', 'qte' => 12), 1 => array( 'objet' => 'capsules', 'qte' => 57)));

brackets

	[nom] = 'toto'
	[collections][0][objet] = 'porte clefs'
	(avec pour contexte [collections][1]) ~[qte]= 57
	~key= 1
	~up[0]=('objet'=>'porte-clefs','qte' => 12)
	~upkey='collections'

slashes

	/nom='toto'
	/collections =  'porte clefs'
	(avec pour contexte /collections/1) qte= 57
	~key= 1
	~up/0=('objet'=>'porte-clefs','qte' => 12)
	~up/~key='collections'


file version 2005-05-27 11:31:03
*/
class arraypath {
/*----------------------------------------------------------------------------------------------
                                                                           parametres par defaut
----------------------------------------------------------------------------------------------*/
	var $parameters=array(
		'ereg_key'=>'[a-zA-Z0-9_@+*#|-]+', // expression reguliere de definition de clef
		'ereg_value'=>'' // expression reguliere de definition de valeur
	);
	//var $syntaxes=array('slashes','brackets');
	var $syntax="slashes";
	var $context=""; // au format slashes, qui est la syntaxe de travail
	var $arrayPathObjectInternalWorkingData=array();
	var $byreference=false;
/*----------------------------------------------------------------------------------------------
                                                                           constructeur eventuel
----------------------------------------------------------------------------------------------*/
	function arrayPath($data=array(),$syntax="slashes") {
		$this->arrayPathObjectInternalWorkingData=&$data;
		$this->syntax=$syntax;
	}
/*----------------------------------------------------------------------------------------------
                                                                              methodes publiques
----------------------------------------------------------------------------------------------*/
	// charge un tableau en mémoire comme données de travail
	/*function loadData(&$data, $byreference=false) {
		if (!is_array($data))
			return false;
		if ($byreference===true) {
			$this->byreference=true;
			$this->arrayPathObjectInternalWorkingData=& $data;
		} else // copy
			$this->arrayPathObjectInternalWorkingData=$data;
		$this->setContext('/','slashes');
		return true;
	}*/
	function loadData($data) {
		if (!is_array($data))
			return false;
		$this->arrayPathObjectInternalWorkingData=& $data;
		$this->setContext('/','slashes');
		return true;
	}
	
	// determine la syntaxe par defaut
	function setSyntax($syntax) {
		switch ($syntax) {
		case 'slashes':
		case 'brackets':
			$this->syntax=$syntax;
			return true; break;
		default:
			return false; break;
		}
	}
	function getSyntax() {
		return $this->syntax;
	}
	function setContext($context,$syntax=NULL) {
		if (!$this->context=$this->fullSlashesPath($context,$syntax)) return false;
		else return true;
	}
	
	function getContext($syntax=NULL) {
		$context=$this->context;
		if ($syntax=='brackets' or ($syntax===NULL and $this->syntax=='brackets'))
		$context=$this->slashesToBrackets($context);
		return $context;
	}
	
	// renvoie la ou les valeurs pointées
	function get($path=NULL,$syntax=NULL) {
		if ($path===NULL) return $this->arrayPathObjectInternalWorkingData;
		$data=$this->getRef($path,$syntax);// echo affvar($data);
		//echo "<p>arraypath: getting $path with context {$this->context}, (with syntax $syntax) = ".affvar($data)."</p>";
		if (!$data) return false;
		else if (count($data)==1) return current($data);
		else return $data;
	}
	
	// affecte la ou les valeurs pointées
	function set($path,$value,$syntax=NULL) {
		$refs=& $this->getRef($path,$syntax);// echo $path."=".affvar($refs);
		if (!is_array($refs)) {
			trigger_error("arraypath: couldn't get refs for $path",E_USER_NOTICE);
			return false;
		}
		foreach (array_keys($refs) as $ref) {
			$refs[$ref]=$value;
		}
		return true;
	}
	// retourne un tableau de référence sur le(s) chemin(s) pointé(s)
	function & getRef($path,$syntax=NULL) {
		// transcription en chemin slashes complet
		if (!$slashespath=$this->fullSlashesPath($path,$syntax)) {
			trigger_error("arrayPath: unvalid syntax $syntax for path $path");
			return false;
		}
		return $this->digForRefs($slashespath);
	}
	// etablit la valeur pour le(s) chemin pointé
	//function setRef($path,$value,$syntax)
	
	// retourne une expression régulière permettant d'identifier un chemin correct
	function makePathEreg($syntax=NULL) {
		if ($syntax!="slashes" and $syntax!="brackets") $syntax=$this->syntax;
		switch ($syntax) {
		case "brackets":
			/*return "~?(\[".$this->parameters['ereg_key']."\]"
			.'((\['.$this->parameters['ereg_key'].'\])|[*]|up)*'
			.'(key)?)';*/
			return "~|" // chemin relatif seul ou
			."(~?\[".$this->parameters['ereg_key']."\]" // chemin relatif optionnel suivi d'au moins une etape-clef
			.'((\['.$this->parameters['ereg_key'].'\])|[*]|up)*' // puis de zero ou plus etapes-clefs, * ou up
			.')' 
			.'(key)?'; // se terminant optionnellement par key
			break;
		case "slashes":
		default:
			/*return '(/|((/'.$this->parameters['ereg_key'].')|(/~[*]))*' // ne doit pas commencer par ~up
			.'((/'.$this->parameters['ereg_key'].')|(/~up))' // ne doit pas se terminer par /~*
			.'(/~key)?)';*/
			/*return '(/' // chemin vide
			.'|(/'.$this->parameters['ereg_key'].'' // au moins un chemin normal
			.'((/'.$this->parameters['ereg_key'].')|(/~up)|(/~[*]))*)'
			.'(/~key)?)';*/
			return '/' // chemin vide
			.'|((/'.$this->parameters['ereg_key'].')|(/~[*]))' // au moins un chemin normal ou /~*
			.'(/'.$this->parameters['ereg_key'].')|(/~up)|(/~[*]))*' // suivi d'un ou plusieurs chemins normaux ou ~up
			.'(/~key)?';
			break;
		}
	}
/*----------------------------------------------------------------------------------------------
                                                                                méthodes privées
----------------------------------------------------------------------------------------------*/
	// traduction en chemin slashes complet
	function fullSlashesPath($path,$syntax=NULL) {
		if ($syntax!="slashes" and $syntax!="brackets") $syntax=$this->syntax;
		//if ($syntax=="slashes") $path=$this->slashesToBrackets($path);
		if ($syntax=="brackets") $path=$this->bracketsToSlashes($path);
		if ($path=='' or $path[0]!="/") $path=$this->context.'/'.$path;
		// suppression de slash final au besoin
		if (substr($path,-1,1)=='/') $path=substr($path,0,-1);
		if (!$this->checkSlashesSyntax($path)) {
			trigger_error("arrayPath: failed to translate $path to full slashes syntax");
			return false;
		}
		return $path;
	}
	
	function slashesToBrackets($path) {
		// chemin contextuel
		if (!$path or $path[0]!='/') $path='~'.$path;
		else $path=substr($path,1);
		$steps=explode('/',$path);
		$newpath="";
		foreach ($steps as $step) {
			switch ($step) {
			case '~up': $newpath.="up"; break;
			case '~key': $newpath.="key"; break;
			case '~any':  $newpath.="any"; break;
			case '*':  $newpath.="*"; break;
			default: $newpath.="[$step]"; break;
			}
		}
		/*$path=str_replace('~up','',$path);
		$path=ereg_replace('([^/*~]+)','[\\1]',$path);
		$path=str_replace('/','',$path);*/
		return $newpath;
	}
	
	function bracketsToSlashes($path) {
		// chemin contextuel
		$oldpath=$path;
		if ($path=='' or $path[0]!='~') $path='/'.$path;
		else $path=substr($path,1);
		// up
		$path=ereg_replace('([^[])?up([^]])?','\\1~up\\2',$path);
		// key
		$path=ereg_replace('([^[])?key([^]])?','\\1~key\\2',$path);
		// any
		$path=ereg_replace('([^[])?\*([^]])?','\\1~*\\2',$path);
		// *
		$path=ereg_replace('\[([^][]+)\]','\\1/',$path);
		if (substr($path,-1,1)=='/') $path=substr($path,0,-1);
		//echo $path.'<br />';
		//echo "<p>brackets $oldpath to slashes $path";
		return $path;
	}
	
	function checkSlashesSyntax($path) {
		// ajout slash initial ???
		if ($path=='' or $path[0]!='/') $path='/'.$path;
		//ereg de verif
		$ereg='^'.$this->makePathEreg('slashes').'$';// echo $ereg.':'.$path;
		if (ereg($ereg,$path)) return true;
		trigger_error("arrayPath: invalid slash syntax $path , check ereg is $ereg",E_USER_NOTICE);
	}
	// renvoie un tableau de références, crée les items si possible et nécessaire
	function digForRefs($path,$context=NULL) {
		if ($context===NULL) $context=$this->context;
		// syntaxe de travail = slashes
		//if ($this->syntax='brackets') $path=$this->bracketsToSlashes($path);
		$refs=array();
		// traitement des up
		$path=$this->treatUp($path);
		// detection du premier aiguillage multiple
		$ereg='^(.*)/~[*](.*)$';
		if (ereg($ereg,$path,$found)) {
			// recup du chemin pointé
			//echo affvar($found);
			$debut=$found[1]; if ($debut=='/~*') $debut=''; // echo $debut;
			$fin=$found[2];
			$mount=&$this->evalPath($debut);// echo affvar($mount);
			$keys=array_keys($mount);
			//if (!is_array($mount)) return false;
			$n=0;
			foreach ($keys as $key) {
				$workpath=$debut.'/'.$key/*.'/'*/.$fin;// echo $workpath;
				//echo affvar($this->evalPath($workpath));
				//echo affvar($this->digForRefs($workpath));
				$refs=array_merge($refs,$this->digForRefs($workpath));
				//$refs=array_merge($refs,$this->digForRefs($found[1].'/'.$key));
			}
			//echo affvar($found);
		} else {
			$refs[]=&$this->evalPath($path);
		}
		//echo $path."=>".affvar($refs);
		return $refs;
	}
	// retourne une référence sur le chemin, le créant si nécessaire avec la valeur donnée
	function & evalPath($path) {
		// recup de clef si besoin
		if ($key=$this->treatKey($path)) return $key;
		if ($path=='/') $path='';
		$arpath=ereg_replace('/('.$this->parameters['ereg_key'].')',"['\\1']",$path);
		//echo $path;
		$eval='if (!isset($this->arrayPathObjectInternalWorkingData'.$arpath.')) { $this->arrayPathObjectInternalWorkingData'.$arpath.'=NULL; } '
		.'return $this->arrayPathObjectInternalWorkingData'.$arpath.';';
		/*$value=eval($eval);*/
		//echo '<p>evaluating '.$path." => ".highlight_string($eval,true).'</p>';
		echo highlight_string($eval,true);
		//trigger_error($notice,E_USER_NOTICE);/**/
		eval('echo "<p><strong>".get_class($this)."</strong></p>";');
		//return eval($eval);
		return NULL;
	}
	
	function setPath($path,$value=NULL) {
		// '/' = racine du tableau
		if ($path=='/') $path='';
		// traduction en code php /key = ['key']
		$arpath=ereg_replace('/('.$this->parameters['ereg_key'].')',"['\\1']",$path);
		// transcription de la valeur en declaration PHP de la valeur
		$varDeclaration=$this->varToDeclaration($value);
		// l'affectation ci dessous fonctionne
		$eval='$value='.$varDeclaration.";\n";
		// la ligne ci dessous tue le script si $value est un tableau
		$eval.='$this->arrayPathObjectInternalWorkingData'.$arpath.' = $value;';
		//if (is_array($value)) return $value;
		return eval($eval);
	}
	
	function treatUp($path) {
		$ereg='/('.$this->parameters['ereg_key'].')/~up';
		while (ereg($ereg,$path)) {
			$path=ereg_replace($ereg,'',$path);
		}
		return $path;
	}
	
	// retourne la valeur de la clef en cas de commande ~key
	function treatKey($path) {
		$ereg='/('.$this->parameters['ereg_key'].')/~key$';
		if (ereg($ereg,$path,$found))
			return $found[1];
		else return false;
	}
	
	// retourne la declaration php d'une variable (recursif)
	// si key est nul, seule la partie valeur est retournée
	function varToDeclaration($var,$key=NULL) {
		switch (true) {
		case is_bool($var):
			if ($var) $r='true';
			else $r='false';
			break;
		case is_null($var):
			$r='NULL';
			break;
		case is_string($var):
			$r="'".str_replace("'","\'",$var)."'";
			break;
		//case is_int($var):
		//case is_double($var):
		//case is_float($var):
		case is_numeric($var):
		//case 'double':
		//case 'decimal':
		//case 'integer':
		//case 'float':
		//case 'real':
			$r=$var;
			break;
		case is_array($var):
			$r="array(\n";
			foreach ($var as $key2 => $value) {
				$r.="'$key2'=>".$this->varToDeclaration($value).",";
			}
			// suppression derniere virgule superflue
			if (substr($r,-1,1)==',') $r=substr($r,0,-1);
			$r.=")";
			break;
		default:
			//echo gettype($var).':'.$var;
			$r='NULL';
			break;
		}
		//echo affvar($r,'var_declaration');
		if ($key!==NULL) return $key."=".$r.";";
		else return $r;
	}
/*----------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------*/
}
?>
